package cn.uncode.springcloud.starter.log.configuration;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.MethodParameter;
import org.springframework.core.io.InputStreamSource;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.multipart.MultipartFile;

import cn.uncode.springcloud.starter.boot.app.AppInfo;
import cn.uncode.springcloud.starter.log.annotation.Log;
import cn.uncode.springcloud.starter.log.service.LogHolder;
import cn.uncode.springcloud.starter.log.service.LogService;
import cn.uncode.springcloud.starter.log.service.LogStroeModel;
import cn.uncode.springcloud.starter.log.service.LogToKafkaServiceImpl;
import cn.uncode.springcloud.utils.json.JsonUtil;
import cn.uncode.springcloud.utils.net.WebUtil;
import cn.uncode.springcloud.utils.obj.BeanUtil;
import cn.uncode.springcloud.utils.obj.ClassUtil;
import cn.uncode.springcloud.utils.string.StringUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * Spring boot 控制器 请求日志，方便代码调试
 *
 * @author Juny
 */
@Slf4j
@Aspect
@Configuration
public class LoggerAspectAutoConfiguration {
	
	@Autowired
	private ApplicationContext context;
	
    @Bean
    public LogService logService() {
    	log.info("===Uncode-starter===LoggerAspectAutoConfiguration===>LogService inited...");
        return new LogToKafkaServiceImpl();
    }

	/**
	 * AOP 环切 控制器 R 返回值
	 *
	 * @param point JoinPoint
	 * @return Object
	 * @throws Throwable 异常
	 */
	@Around(
//			"execution(!static cn.uncode.springcloud.starter.result.R<*> *(..)) &&" +
			"execution(!static * *(..)) &&" +
			"(@within(org.springframework.stereotype.Controller) || " +
			"@within(org.springframework.web.bind.annotation.RestController))"
	)
	public Object aroundApiR(ProceedingJoinPoint point) throws Throwable {
		return this.aroundProcess(point);
	}

	/**
	 * 公共环绕的方法
	 * @param point point
	 * @return Object
	 * @throws Throwable
	 */
	private Object aroundProcess(ProceedingJoinPoint point) throws Throwable {
		MethodSignature ms = (MethodSignature) point.getSignature();
		Method method = ms.getMethod();
		Object[] args = point.getArgs();
		final Map<String, Object> paraMap = new HashMap<>(16);
		for (int i = 0; i < args.length; i++) {
			MethodParameter methodParam = ClassUtil.getMethodParameter(method, i);
			PathVariable pathVariable = methodParam.getParameterAnnotation(PathVariable.class);
			if (pathVariable != null) {
				continue;
			}
			RequestBody requestBody = methodParam.getParameterAnnotation(RequestBody.class);
			Object object = args[i];
			// 如果是body的json则是对象
			if(object instanceof BeanPropertyBindingResult) {
				continue;
			}
			if (requestBody != null && object != null) {
				paraMap.putAll(BeanUtil.toMap(object));
			} else {
				RequestParam requestParam = methodParam.getParameterAnnotation(RequestParam.class);
				String paraName;
				if (requestParam != null && StringUtil.isNotBlank(requestParam.value())) {
					paraName = requestParam.value();
				} else {
					paraName = methodParam.getParameterName();
				}
				paraMap.put(paraName, object);
			}
		}
		HttpServletRequest request = WebUtil.getRequest();
		String requestURI = request.getRequestURI();
		String requestMethod = request.getMethod();
		// 处理 参数
		List<String> needRemoveKeys = new ArrayList<>(paraMap.size());
		paraMap.forEach((key, value) -> {
			if (value instanceof HttpServletRequest) {
				needRemoveKeys.add(key);
				paraMap.putAll(((HttpServletRequest) value).getParameterMap());
			} else if (value instanceof HttpServletResponse) {
				needRemoveKeys.add(key);
			} else if (value instanceof InputStream) {
				needRemoveKeys.add(key);
			} else if (value instanceof MultipartFile) {
				String fileName = ((MultipartFile) value).getOriginalFilename();
				paraMap.put(key, fileName);
			} else if (value instanceof InputStreamSource) {
				needRemoveKeys.add(key);
			} else if (value instanceof WebRequest) {
				needRemoveKeys.add(key);
				paraMap.putAll(((WebRequest) value).getParameterMap());
			}
		});
		needRemoveKeys.forEach(paraMap::remove);
		Log trace = ClassUtil.getAnnotation(method, Log.class);
		// 打印请求头
		StringBuffer headerBuffer = new StringBuffer();
		Enumeration<String> headers = request.getHeaderNames();
		while (headers.hasMoreElements()) {
			String headerName = headers.nextElement();
			String headerValue = request.getHeader(headerName);
			if(headerValue.length() > 100) {
				headerValue = headerValue.substring(0, 100) + "...";
			}
			headerBuffer.append(headerName).append("=").append(headerValue).append(",\n");
		}
		// 打印执行时间
		long startNs = System.nanoTime();
		Object result = null;
		try {
			result = point.proceed();
			return result;
		} finally {
			long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
			String rtJson = String.valueOf(JsonUtil.toJson(result));
			if(rtJson.length() > 1000) {
				rtJson = rtJson.substring(0, 1000)+"...";
			}
			log.info("\n========================>>>  Request Start  <<<========================\n" +
							"===>URL:{}:{}\n" +
							"===>Parameters: {}\n" +
							"===>Headers: {}\n" +
							"===>IP: {}\n" +
							"===>ClassMethod: {}\n" +
							"===>Result: {}\n" +
							"===>TookMillisSecond: {}\n" +
							"========================>>>  Request  End   <<<========================\n",
					requestMethod, requestURI,
					JsonUtil.toJson(paraMap),
					JsonUtil.toJson(headerBuffer.toString()),
					WebUtil.getIP(),
					ms.getDeclaringTypeName() + "." + ms.getName(),
					rtJson,
					tookMs);
			// 存日志信息
			saveTrace(trace, request, paraMap, ms.getDeclaringTypeName(), ms.getName(), null);
		}
	}
	
	private void saveTrace(Log trace, HttpServletRequest request, Map<String, Object> reqestMap ,
			 String className, String methodName, Map<String, Object> responseMap) {
		LogStroeModel sendingLog = new LogStroeModel();
		sendingLog.setAppName(AppInfo.getAppName());
		sendingLog.setRequestMethod(request.getMethod());
		sendingLog.setRequestURI(request.getRequestURI());
		sendingLog.setRequestData(reqestMap);
		Enumeration<String> headers = request.getHeaderNames();
		while(headers.hasMoreElements()) {
			String name = headers.nextElement();
			sendingLog.addHeader(name, request.getHeader(name));
		}
		sendingLog.setIp(WebUtil.getIP(request));
		sendingLog.setUserId(LogHolder.getUserId());
		sendingLog.setClassName(className);
		sendingLog.setMethodName(methodName);
		String content = null;
		if(trace != null) {
			sendingLog.setModle(trace.model());
			sendingLog.setTarget(trace.target());
			sendingLog.setOptionType(trace.type());
			if(StringUtils.isNotBlank(trace.value())) {
				content = elFormart(trace.value(), reqestMap, responseMap);
			}else {
				if(StringUtils.isNotBlank(LogHolder.getValueOfLogAnnotation())) {
					content = elFormart(LogHolder.getValueOfLogAnnotation(), reqestMap, responseMap);
				}
			}
		}else {
			if(StringUtils.isNotBlank(LogHolder.getValueOfLogAnnotation())) {
				content = elFormart(LogHolder.getValueOfLogAnnotation(), reqestMap, responseMap);
			}
		}
		sendingLog.setLogContent(content);
		LogService logService = context.getBean(LogService.class);
		logService.sendMsg(sendingLog);
	}
	
	public String elFormart(String text, Map<?, ?> requestMap, Map<?, ?> responseMap) {
		List<Map<?, ?>> requestList = new ArrayList<>(); 
		List<Map<?, ?>> responseList = new ArrayList<>();
		requestList.add(requestMap);
		responseList.add(responseMap);
		return elFormart(text, requestList, responseList);
	}
	
	public String elFormart(String text, List<Map<?, ?>> requestList, List<Map<?, ?>> responseList) {
		String rtText = text;
		Pattern pattern = Pattern.compile("\\{[a-z]+:[A-Za-z0-9\\[\\].]+\\}");
		Matcher matcher = pattern.matcher(text);
		// 循环，字符串中有多少个符合的，就循环多少次
		while (matcher.find()) {
			// 每一个符合正则的字符串
			String e = matcher.group();
			// 截取出括号中的内容
			int index = e.indexOf(":");
			String type = e.substring(1, index);
			String substring = e.substring(index + 1, e.length() - 1);
			// 取值
			String[] vals = substring.split("\\.");
			Object value = null;
			for (int i = 0; i < vals.length; i++) {
				String fd = vals[i];
				int fdIndex = 0;
				int start = fd.indexOf("[");
				int end = fd.indexOf("]");
				if (start != -1) {
					fdIndex = Integer.parseInt(fd.substring(start + 1, end));
					fd = fd.substring(0, start);
				}
				if ("in".equals(type.toLowerCase())) {
					if (i == 0) {
						value = requestList.get(fdIndex).get(fd);
					} else {
						if (fdIndex > 0) {
							List<Map<?, ?>> list = (List<Map<?, ?>>) value;
							value = list.get(fdIndex).get(fd);
						} else {
							Map<?, ?> list = (Map<?, ?>) value;
							value = list.get(fd);
						}
					}
				} else {
					if (i == 0) {
						value = responseList.get(fdIndex).get(fd);
					} else {
						if (fdIndex > 0) {
							List<Map<?, ?>> list = (List<Map<?, ?>>) value;
							value = list.get(fdIndex).get(fd);
						} else {
							Map<?, ?> list = (Map<?, ?>) value;
							value = list.get(fd);
						}
					}
				}
				if (i == vals.length - 1) {
					rtText = rtText.replace(e, String.valueOf(value));
				}
			}
		}
		return rtText;
	}

}
